package com.common.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.parameter.DefaultParameterHandler;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;

import com.common.dialect.Dialect;
import com.common.dialect.MySql5Dialect;
import com.common.dialect.OracleDialect;
import com.common.dialect.SqlServerDialect;
import com.common.page.PageContext;
import com.common.page.PageKeyContext;

/**
* @ClassName: PaginationInterceptor
* @Description: Mybaits分页拦截器
* @author 冰上浮云
* @date 2013年12月9日 下午9:51:25
*
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class PaginationInterceptor implements Interceptor
{
    
    private final static Log log = LogFactory.getLog(PaginationInterceptor.class);
    
    @SuppressWarnings("unused")
    @Override
    public Object intercept(Invocation invocation)
        throws Throwable
    {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        
        BoundSql boundSql = statementHandler.getBoundSql();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler);
        RowBounds rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
        
        // 重写sql  
        String originalSql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
        originalSql=originalSql.replaceAll("[\r\n]", " ");
        
        //只对打上分页注解的查询才进行分页
        String strSqlFirstWorld = originalSql.substring(0, originalSql.indexOf(' '));
        strSqlFirstWorld = strSqlFirstWorld.trim().toUpperCase();
        if ((!"SELECT".equals(strSqlFirstWorld))||(!PageContext.getContext().isPagination()))
        {
            return invocation.proceed();
        }
        
        //分页参数设置
        int offset = 0;
        int limit = 0;
        int totalRows = 0;
        int pageSize = PageKeyContext.PAGE_KEY_PAGE_SIZE;
        
        //得到总记录数
        totalRows = getTotalRows(originalSql, invocation, metaStatementHandler, boundSql);
        PageContext context = PageContext.getContext();
        //总记录数为0也无需分页
        if(0==totalRows)
        {
        	context.init(totalRows, pageSize, context.getCurrentPage());
            return invocation.proceed();
        }
        
        //如果每页记录数不为0，使用设置的参数
        if (0 != context.getPageSize())
        {
            pageSize = context.getPageSize();
        }
        
        if(context.getPageStartRow()<0)
        {
        	context.setPageStartRow(0);
        }
        context.init(totalRows, pageSize, context.getCurrentPage());
        if (context != null)
        {
            offset = context.getPageStartRow();
            limit = context.getPageSize();
            totalRows = context.getTotal();
        }
        else if (rowBounds == null || rowBounds == RowBounds.DEFAULT)
        {
            return invocation.proceed();
        }
        else
        {
            offset = rowBounds.getOffset();
            limit = rowBounds.getLimit();
        }
        
        Configuration configuration = (Configuration)metaStatementHandler.getValue("delegate.configuration");
        
        Dialect.Type databaseType = null;
        
        try
        {
            databaseType = Dialect.Type.valueOf(configuration.getVariables().getProperty("dialect").toUpperCase());
        }
        catch (Exception e)
        {
            
            //ignore
            
        }
        
        if (databaseType == null)
        {
            
            throw new RuntimeException("the value of the dialect property in configuration.xml is not defined : "
                + configuration.getVariables().getProperty("dialect"));
            
        }
        
        Dialect dialect = null;
        
        switch (databaseType)
        {
        
            case MYSQL:
                dialect = new MySql5Dialect();
                break;
            
            case ORACLE:
                dialect = new OracleDialect();
                break;
            
            case SQLSERVER:
                dialect = new SqlServerDialect();
                break;
        }
        
        String pageSql = dialect.getLimitString(originalSql, offset, limit);
        
        metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
        metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
        
        log.debug("生成分页SQL : " + boundSql.getSql());
        
        return invocation.proceed();
    }
    
    @Override
    public Object plugin(Object target)
    {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的  
        // 次数  
        if (target instanceof StatementHandler)
        {
            return Plugin.wrap(target, this);
        }
        else
        {
            return target;
        }
    }
    
    @Override
    public void setProperties(Properties arg0)
    {
        // Auto-generated method stub
        
    }
    
    /** 设置总页数
     * <功能详细描述>
     * @param originalSql
     * @param invocation
     * @param metaStatementHandler
     * @param boundSql
     * @return
     * @throws SQLException
     * @see [类、类#方法、类#成员]
     */
    private int getTotalRows(String originalSql, Invocation invocation, MetaObject metaStatementHandler,
        BoundSql boundSql)
        throws SQLException
    {
        //总页数
        int totalPage = 0;
        
        StringBuffer countSql = new StringBuffer(originalSql.length() + 100);
        countSql.append("select count(1) from (").append(originalSql).append(") t");
        Connection connection = (Connection)invocation.getArgs()[0];
        PreparedStatement countStmt = connection.prepareStatement(countSql.toString());
        
        //动态绑定参数
        MappedStatement mappedStatement = (MappedStatement)metaStatementHandler.getValue("delegate.mappedStatement");
        Object parameterObject = boundSql.getParameterObject();
        BoundSql countBS =
            new BoundSql(mappedStatement.getConfiguration(), countSql.toString(), boundSql.getParameterMappings(),
                parameterObject);
        setParameters(countStmt, mappedStatement, countBS, parameterObject);
        ResultSet rs = countStmt.executeQuery();
        if (rs.next())
        {
            totalPage = rs.getInt(1);
        }
        //        rs.close();
        //        countStmt.close();
        //        connection.close();
        
        return totalPage;
    }
    
    /** 
     * 对SQL参数(?)设值 
     *  
     * @param ps 
     * @param mappedStatement 
     * @param boundSql 
     * @param parameterObject 
     * @throws SQLException 
     */
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
        Object parameterObject)
        throws SQLException
    {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }
    
}
